﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;
using System.Linq.Expressions;
using System.Collections.ObjectModel;
using System.Concurrency;

namespace ReactiveCode.Expressions
{
    using Observables;
    using Observables.Expressions;
    using Expressions;
    using Util;

    interface IExpressionParent
    {
        IList<ReactiveExpression> Children { get; }
    }
    public partial class ReactiveExpression : IStateDecorator
    {
        #region Hierarchy
        Variable<ReactiveExpression> _parent = new Variable<ReactiveExpression>();
        internal ReactiveExpression Parent
        {
            get { return _parent.Value; }
            set
            {
                Contract.Requires(!(this is IRootExpression));
                Contract.Requires((value == null) != (Parent == null));
                _parent.Value = value;
            }
        }

        internal IEnumerable<ReactiveExpression> Ancestors()
        {
            return MeAndAncestors().Skip(1);
        }
        internal IEnumerable<ReactiveExpression> MeAndAncestors()
        {
            for (var expr = this; expr != null; expr = expr.Parent)
                yield return expr;
        }

        Variable<ReactiveExpression> _root = new Variable<ReactiveExpression>();
        internal ReactiveExpression Root
        {
            get
            {
                Contract.Assume(_root.Value == null || _root.Value is IRootExpression);
                return _root.Value;
            }
            set
            {
                Contract.Requires(this is IRootExpression);
                _root.Value = value;
            }
        }

        internal virtual ReactiveHost Host
        {
            get { return Root != null ? Root.Host : null; }
        }
        #endregion

        #region Enabled property

        public bool Enabled
        {
            get { return _enabled.Value; }
            internal set
            {
                Contract.Requires(!value || Host != null);
                _enabled.Value = value;
            }
        }
        Variable<bool> _enabled = new Variable<bool>(false);
        internal IObservable<bool> EnabledProperty { get; private set; }
        internal IDisposable BindEnabled(ReactiveExpression other)
        {
            Contract.Requires(other != null && other != this);
            return _enabled.Bind(other._enabled);
        }

        #endregion

        public string Name { get; set; }

        internal ReactiveExpression()
        {
            EnabledProperty = _enabled.DistinctUntilChanged();
            _parent = new Variable<ReactiveExpression>();
            _parent.SelectMany(x => x._root).Subscribe(_root);
        }

        public virtual void Dispose()
        {
            Enabled = false;
            foreach(var handle in _weakHandles)
                handle.Dispose();
            _weakHandles.Clear();
        }

        #region Current

        [ThreadStatic]
        static Stack<ReactiveExpression> _currentExprs;

        internal void PushCurrent()
        {
            if (_currentExprs == null)
                _currentExprs = new Stack<ReactiveExpression>();
            _currentExprs.Push(this);
        }
        internal void PopCurrent()
        {
            if (_currentExprs == null)
                throw new InvalidOperationException();

            for (; ; )
            {
                if (_currentExprs.Count == 0)
                    throw new InvalidOperationException();
                if (this == _currentExprs.Pop())
                    break;
            }
        }

        internal static ReactiveExpression Current
        {
            get
            {
                if (_currentExprs == null || !_currentExprs.Any())
                    return null;
                else
                    return _currentExprs.Peek();
            }
        }

        internal bool IsHigherPriority(ReactiveExpression than)
        {
            return CompareOrder(this, than) > 0;
        }
        int? CompareWithCurrent()
        {
            return CompareOrder(this, Current);
        }
        internal bool ShouldProcessCurrentChange()
        {
            int? cmp = CompareWithCurrent();
            if (cmp != null && cmp <= 0)
                return false;
            return true;
        }

        #endregion

        #region State changes

        internal StateChangeScheduler Scheduler
        {
            get { return Host.Scheduler; }
        }

        internal virtual bool MarkAsCurrent
        {
            get { return true; }
        }

        LinkedList<IDisposable> _weakHandles = new LinkedList<IDisposable>();

        IObservable<T> IStateDecorator.Apply<T>(IObservable<T> state)
        {
            IDisposable handle;
            state = state.MakeWeak(out handle);
            _weakHandles.AddLast(handle);

            state = state.Where(_ => ShouldProcessCurrentChange());
            return Observable.Defer(() => Scheduler.Schedule(state, this));
        }
        IObserver<T> IStateDecorator.Apply<T>(IObserver<T> state)
        {
            return Observer.Create<T>(
                onNext: x =>
                {
                    Contract.Assert(Scheduler != null);
                    Scheduler.ScheduleStateMutation(delegate
                    {
                        bool markAsCurrent = MarkAsCurrent;
                        if (markAsCurrent)
                            PushCurrent();
                        try
                        {
                            state.OnNext(x);
                        }
                        finally
                        {
                            if (markAsCurrent)
                                PopCurrent();
                        }
                    });
                }, 
                onError: state.OnError, 
                onCompleted: state.OnCompleted);
        }
        IObservable<T> PrepareObservable<T>(IObservable<T> observable)
        {
            observable = observable.SubscribeIf(EnabledProperty);
            return Observable.CreateWithDisposable<T>(o => observable.Subscribe(
                onNext: o.OnNext,
                onError: RaiseError,
                onCompleted: o.OnCompleted
            ));
        }
        internal IObservable<T> Extract<T>(ObservableFactory<IObservable<T>> factory)
        {
            return PrepareObservable(factory(this));
        }
        internal ISubject<T> Extract<T>(ObservableFactory<ISubject<T>> factory)
        {
            var subject = factory(this);
            return Subject.Create(
                PrepareObservable(subject),
                subject
            );
        }
        internal IObservableEnumerable<T> Extract<T>(ObservableFactory<IObservableEnumerable<T>> factory)
        {
            return factory(this); // TODO: apply decorator on IObservableEnumerable<T> and make weak
        }

        #endregion

        #region Exception handling

        internal virtual void OnError(ReactiveUnhandledExceptionEventArgs e)
        {
            Contract.Requires(e != null);
            Enabled = false;
            if (Parent != null)
                Parent.OnError(e);
        }
        void RaiseError(Exception exception)
        {
            Contract.Requires(exception != null);
            var e = new ReactiveUnhandledExceptionEventArgs(exception);
            OnError(e);
            if (!e.IsHandled)
                throw new ReactiveException(e.Exception);
        }

        #endregion

        public override string ToString()
        {
            string result = Name ?? base.ToString();
            if (!Enabled)
                result = "!" + result;
            return result;
        }
    }
}